package com.youlin.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlin.dto.*;
import com.youlin.dto.req.AddAMReqDto;
import com.youlin.dto.req.FindAllAmReqDto;
import com.youlin.dto.req.UpdAMStatusReqDto;
import com.youlin.dto.res.*;
import com.youlin.entities.*;
import com.youlin.mapper.BygAppointmentMapper;
import com.youlin.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlin.untils.*;
import org.apache.commons.lang.StringUtils;
import org.assertj.core.util.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

@Service
public class BygAppointmentServiceImpl extends ServiceImpl<BygAppointmentMapper, BygAppointment> implements BygAppointmentService {

    @Value("${spring.profiles.active}")
    private String profile;
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private BygAppointmentMapper bygAppointmentMapper;
    @Autowired
    private BygUndertakerService bygUndertakerService;
    @Autowired
    private BygAdminService bygAdminService;
    @Autowired
    private BygDeadManService bygDeadManService;
    @Autowired
    private BygFarewellHallService bygFarewellHallService;
    @Autowired
    private BygRelationService bygRelationService;
    @Autowired
    private BygAppointmentService bygAppointmentService;
    @Autowired
    private BygPhotoService bygPhotoService;
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    @Autowired
    TransactionDefinition transactionDefinition;

    @Override
    public PageDataResDto<List<AppointmentResDto>> findAllAppointments(BygAdmin currentAdmin,
                                               FindAllAmReqDto data, PageInfo pageInfo) {
        List<AppointmentResDto> finalAppointments = Lists.newArrayList();
        CompletableFuture<Void> future0 = CompletableFuture.runAsync(() -> {
            QueryWrapper<BygAppointment> queryWrapper = new QueryWrapper<>();
            if (currentAdmin.getRoleId() == MyEnum.ROLE_OPERATOR_ID.getValue())
                queryWrapper.eq("operator_id", currentAdmin.getId())
                        .lt("status", MyEnum.AM_PRE_REVIEW.getValue());
            if (currentAdmin.getRoleId() == MyEnum.ROLE_REVIEWER_ID.getValue())
                queryWrapper.nested(qw -> qw.eq("reviewer_id", currentAdmin.getId())
                        .or().eq("status", MyEnum.AM_PRE_REVIEW.getValue()));
            if (data.getFhId() != null) queryWrapper.eq("fh_id", data.getFhId());
            if (data.getStatus() != null) queryWrapper.eq("status", data.getStatus());
            if (data.getUseDate() != null) queryWrapper.eq("use_date", data.getUseDate());
            if (StringUtils.isNotBlank(data.getTakerName()) || StringUtils.isNotBlank(data.getTakerTel())) {
                LambdaQueryWrapper<BygUndertaker> wrapper = Wrappers.lambdaQuery(BygUndertaker.class);
                if (StringUtils.isNotBlank(data.getTakerTel())) wrapper.eq(BygUndertaker::getTel, data.getTakerTel());
                if (StringUtils.isNotBlank(data.getTakerName())) wrapper.eq(BygUndertaker::getName, data.getTakerName());
                List<Integer> takerIds = bygUndertakerService.list(wrapper).parallelStream()
                        .map(BygUndertaker::getId).collect(Collectors.toList());
                if (takerIds.size() > 0) queryWrapper.in("undertaker_id", takerIds);
                else queryWrapper.in("undertaker_id", Arrays.asList(-1));
            }
            if (StringUtils.isNotBlank(data.getDeadName())) {
                LambdaQueryWrapper<BygDeadMan> wrapper = Wrappers.lambdaQuery(BygDeadMan.class)
                        .eq(BygDeadMan::getName, data.getDeadName());
                List<Integer> dmIds = bygDeadManService.list(wrapper).parallelStream()
                        .map(BygDeadMan::getId).collect(Collectors.toList());
                if (dmIds.size() > 0) queryWrapper.in("dm_id", dmIds);
                else queryWrapper.in("dm_id", Arrays.asList(-1));
            }
            IPage<BygAppointment> page = page(new Page<>(pageInfo.getPage(), pageInfo.getSize()), queryWrapper.orderByDesc("id"));
            pageInfo.setTotal(page.getTotal());
            finalAppointments.addAll(page.getRecords().parallelStream().map(a ->
                    BaseService.copyProperties(a, AppointmentResDto.class)).collect(Collectors.toList()));
        }, threadPoolExecutor);

        CompletableFuture<Void> future1 = future0.thenRunAsync(() -> {
            if (finalAppointments.size() > 0) {
                List<Integer> dmIds = finalAppointments.parallelStream().map(AppointmentResDto::getDmId).collect(Collectors.toList());
                List<DMListResDto> dmListResDtos = BaseService.findAndDealWithOneToOne(bygDeadManService.listByIds(dmIds),
                        BygDeadMan::getRelationId, DMListResDto.class, DMListResDto::setRelation,
                        bygRelationService, BygRelation::getId, RelationResDto.class);
                BaseService.dealWithOneToOne(finalAppointments, AppointmentResDto::getDmId, AppointmentResDto.class,
                        AppointmentResDto::setDeadMan, dmListResDtos, DMListResDto::getId, DMListResDto.class);
            }
        }, threadPoolExecutor);

        CompletableFuture<Void> future2 = future0.thenRunAsync(() -> {
            if (finalAppointments.size() > 0)
                BaseService.findAndDealWithOneToOne(finalAppointments, AppointmentResDto::getFhId, AppointmentResDto.class,
                        AppointmentResDto::setFh, bygFarewellHallService, BygFarewellHall::getId, BygFarewellHall.class);
        }, threadPoolExecutor);

        CompletableFuture<Void> future3 = future0.thenRunAsync(() -> {
            if (finalAppointments.size() > 0)
                BaseService.findAndDealWithOneToOne(finalAppointments, AppointmentResDto::getUndertakerId, AppointmentResDto.class,
                        AppointmentResDto::setUndertaker, bygUndertakerService, BygUndertaker::getId, TakerResDto.class);
        }, threadPoolExecutor);

        CompletableFuture<Void> future4 = future0.thenRunAsync(() -> {
            if (finalAppointments.size() > 0)
                BaseService.findAndDealWithOneToOne(finalAppointments, AppointmentResDto::getOperatorId, AppointmentResDto.class,
                        AppointmentResDto::setOperator, bygAdminService, BygAdmin::getId, AdminResDto.class);
        }, threadPoolExecutor);

        CompletableFuture<Void> future5 = future0.thenRunAsync(() -> {
            if (finalAppointments.size() > 0)
                BaseService.findAndDealWithOneToOne(finalAppointments, AppointmentResDto::getReviewerId,
                    AppointmentResDto.class, AppointmentResDto::setReviewer, bygAdminService, BygAdmin::getId, AdminResDto.class);
        }, threadPoolExecutor);

        CompletableFuture<PageDataResDto<List<AppointmentResDto>>> future = CompletableFuture.allOf(future1, future2, future3, future4, future5)
                .thenApplyAsync(r -> new PageDataResDto<List<AppointmentResDto>>(Status.SUCCESS).setData(finalAppointments).setPageInfo(pageInfo));

        try {
            return future.get();
        } catch (Exception e) {
            return new PageDataResDto<>(Status.ERROR);
        }
    }

    @Override
    public BaseDataResDto<List<AMStatusResDto>> findAllAMStatus(Integer roleId) {
        List<AMStatusResDto> arrayList = Lists.newArrayList();
        if (roleId == MyEnum.ROLE_OPERATOR_ID.getValue()) {
            arrayList.add(new AMStatusResDto(MyEnum.AM_CREATE.getMessage(), MyEnum.AM_CREATE.getValue()));
            arrayList.add(new AMStatusResDto(MyEnum.AM_OPERATE.getMessage(), MyEnum.AM_OPERATE.getValue()));
        }
        if (roleId == MyEnum.ROLE_REVIEWER_ID.getValue()) {
            arrayList.add(new AMStatusResDto(MyEnum.AM_PRE_REVIEW.getMessage(), MyEnum.AM_PRE_REVIEW.getValue()));
            arrayList.add(new AMStatusResDto(MyEnum.AM_REVIEWING.getMessage(), MyEnum.AM_REVIEWING.getValue()));
            arrayList.add(new AMStatusResDto(MyEnum.AM_POST_REVIEW.getMessage(), MyEnum.AM_POST_REVIEW.getValue()));
        }
        return new BaseDataResDto<List<AMStatusResDto>>(Status.SUCCESS).setData(arrayList);
    }

    @Override
    @Transactional
    public BaseResDto delAppointment(Integer amId) {
        BygAppointment appointment = getById(amId);
        if (appointment == null) return new BaseResDto(Status.PARAMETERERROR);
        if (appointment.getStatus() > MyEnum.AM_CREATE.getValue())
            return new BaseResDto(Status.ONLYDELCREATE);
        if (removeById(amId)) return new BaseResDto(Status.SUCCESS);
        return new BaseResDto(Status.ERROR);
    }

    @Override
    public BaseResDto editAppointment(BygAdmin currentAdmin, AddAMReqDto data) {
        // 校验逝者生日和忌日是否合法
        LocalDate dBirthday = data.getDeadBirthday();
        LocalDate dDeadday = data.getDeadDeadday();
        if (! dBirthday.isBefore(dDeadday) && dDeadday.isBefore(LocalDate.now())) return new BaseResDto(Status.DDATEERROR);
        LocalTime startTime = data.getStartTime();
        LocalTime endTime = data.getEndTime();
        if (startTime.isAfter(endTime)) return new BaseResDto(Status.USETIMEERROR);
        // 校验告别厅使用日期时间是否存在时间重合冲突
        QueryWrapper<BygAppointment> queryWrapper = new QueryWrapper<BygAppointment>()
                .eq("fh_id", data.getFhId()).eq("use_date", data.getUseDate());
        if (data.getAmId() != null) queryWrapper.ne("id", data.getAmId());
        List<BygAppointment> appointments = list(queryWrapper);
        if (MyTimeUtil.checkAmUseTime(appointments, startTime, endTime))
            return new BaseResDto(Status.USETIMECONFLICT);
        Integer editAmCheck = bygAppointmentMapper.editAmCheck(data.getAmId(),
                data.getDeadName(), data.getTakerName(), data.getTakerTel());
        if (editAmCheck > 0) return new BaseResDto(Status.ALLDUPLICATE);
        BygRelation relation = bygRelationService.getById(data.getRelationId());
        if (relation.getRespectGender() != data.getDeadGender()
                || relation.getTakerGender() != data.getTakerGender())
            return new BaseResDto(Status.RELATIONERROR);
        // 修改/新建 预约数据
        if (data.getAmId() != null) return updAppointment(data);
        else return bygAppointmentService.addAppointment(currentAdmin, data);
        // bygAppointmentService.addAppointment(currentAdmin, data) 非事务方法调用事务方法，使AOP动态代理生效
    }

    // 新建预约数据
    @Override
    @Transactional
    public BaseResDto addAppointment(BygAdmin currentAdmin, AddAMReqDto data) {
        BygUndertaker taker = bygUndertakerService.findTaker(data.getTakerName(), data.getTakerTel());
        if (taker == null) {
            taker = new BygUndertaker().setName(data.getTakerName())
                    .setGender(data.getTakerGender()).setTel(data.getTakerTel());
            if (! bygUndertakerService.save(taker)) return new BaseResDto(Status.ERROR);
        }
        BygDeadMan deadMan = new BygDeadMan()
                .setName(data.getDeadName()).setUpdateTime(LocalDateTime.now())
                .setBirthday(data.getDeadBirthday()).setDeadday(data.getDeadDeadday())
                .setGender(data.getDeadGender()).setRelationId(data.getRelationId());
        if (! bygDeadManService.save(deadMan)) return new BaseResDto(Status.ERROR);
        BygAppointment appointment = new BygAppointment().setOperatorId(currentAdmin.getId())
                .setUseDate(data.getUseDate()).setCreateTime(LocalDateTime.now()).setDmId(deadMan.getId())
                .setUuId(Md5Util.getUUID()).setUndertakerId(taker.getId()).setFhId(data.getFhId())
                .setStartTime(data.getStartTime()).setEndTime(data.getEndTime());
        if (! save(appointment)) return new BaseResDto(Status.ERROR);
        return new BaseResDto(Status.SUCCESS);
    }

    @Override
    public BygAppointment findAppointmentByUuId(String amUuId) {
        LambdaQueryWrapper<BygAppointment> lambdaQueryWrapper = Wrappers
                .lambdaQuery(BygAppointment.class).eq(BygAppointment::getUuId, amUuId);
        return getOne(lambdaQueryWrapper);
    }

    // 修改预约数据
    public BaseResDto updAppointment(AddAMReqDto data) {
        BygAppointment am = getById(data.getAmId());
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3); // 创建线程屏障
        AtomicBoolean rollbackFlag = new AtomicBoolean(false); // 创建事务回滚开关

        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
            if (am == null) {
                rollbackFlag.set(true);
                return;
            }
            // 手动开启事务
            TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
            try {
                BygUndertaker undertaker = new BygUndertaker().setId(am.getUndertakerId())
                        .setName(data.getTakerName()).setGender(data.getTakerGender()).setTel(data.getTakerTel());
                if (! bygUndertakerService.updateById(undertaker)) rollbackFlag.set(true);
            } catch (Exception e) {
                rollbackFlag.set(true);
            } finally {
                try {
                    cyclicBarrier.await();
                } catch (Exception ex) {}
                if (rollbackFlag.get()) dataSourceTransactionManager.rollback(transactionStatus);
                else dataSourceTransactionManager.commit(transactionStatus);
            }
        }, threadPoolExecutor);

        CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> {
            if (am == null) {
                rollbackFlag.set(true);
                return;
            }
            // 手动开启事务
            TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
            try {
                BygDeadMan deadMan = new BygDeadMan().setId(am.getDmId()).setName(data.getDeadName())
                        .setBirthday(data.getDeadBirthday()).setDeadday(data.getDeadDeadday())
                        .setGender(data.getDeadGender()).setRelationId(data.getRelationId());
                if (! bygDeadManService.updateById(deadMan)) rollbackFlag.set(true);
                try {
                    cyclicBarrier.await();
                } catch (Exception ex) {}
                if (rollbackFlag.get()) dataSourceTransactionManager.rollback(transactionStatus);
                else dataSourceTransactionManager.commit(transactionStatus);
            } catch (Exception e) {
                rollbackFlag.set(true);
                dataSourceTransactionManager.rollback(transactionStatus);
                try {
                    cyclicBarrier.await();
                } catch (Exception ex) {}
            }
        }, threadPoolExecutor);

        CompletableFuture<Void> future4 = CompletableFuture.runAsync(() -> {
            if (am == null) {
                rollbackFlag.set(true);
                return;
            }
            // 手动开启事务
            TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
            try {
                am.setFhId(data.getFhId()).setUseDate(data.getUseDate()).setStartTime(data.getStartTime())
                        .setEndTime(data.getEndTime()).setUpdateTime(LocalDateTime.now());
                if (! updateById(am)) rollbackFlag.set(true);
                try {
                    cyclicBarrier.await();
                } catch (Exception ex) {}
                if (rollbackFlag.get()) dataSourceTransactionManager.rollback(transactionStatus);
                else dataSourceTransactionManager.commit(transactionStatus);
            } catch (Exception e) {
                rollbackFlag.set(true);
                dataSourceTransactionManager.rollback(transactionStatus);
                try {
                    cyclicBarrier.await();
                } catch (Exception ex) {}
            }
        }, threadPoolExecutor);

        CompletableFuture<BaseResDto<Object>> future5 = CompletableFuture.allOf(future2, future3, future4).thenApplyAsync(x -> {
            if (rollbackFlag.get()) return new BaseResDto<>(Status.ERROR);
            else return new BaseResDto<>(Status.SUCCESS);
        });

        try {
            redisTemplate.delete("amUuId_dm:" + am.getUuId());
            return future5.get();
        } catch (Exception e) {
            e.printStackTrace();
            return new BaseResDto<>(Status.ERROR);
        }
    }

    @Override
    @Transactional
    public BaseResDto updAMStatus(UpdAMStatusReqDto data) {
        BygAppointment appointment = getById(data.getAmId());
        if (appointment == null) return new BaseResDto(Status.ERROR);
        BygDeadMan deadMan = bygDeadManService.getById(appointment.getDmId());
        Integer status = data.getStatus();
        if (status < appointment.getStatus() || status - appointment.getStatus() > 1)
            return new BaseResDto(Status.UPDAMSTATUSERROR);
        if (status == MyEnum.AM_OPERATE.getValue()) {
            if (MyBeanUtil.isParamError(deadMan)) return new BaseResDto(Status.AMOPERATEERROR);
            LambdaQueryWrapper<BygPhoto> wrapper = Wrappers
                    .lambdaQuery(BygPhoto.class).eq(BygPhoto::getDmId, appointment.getDmId());
            if (bygPhotoService.count(wrapper) == 0) return new BaseResDto(Status.AMOPERATEERROR);
            appointment.setOperateTime(LocalDateTime.now());
        }

        if (status == MyEnum.AM_PRE_REVIEW.getValue()) {
            if (LocalDate.now().isBefore(appointment.getUseDate()))
                return new BaseResDto(Status.AMUNDONE);
            if (LocalDate.now().isEqual(appointment.getUseDate())
                    && LocalTime.now().isBefore(appointment.getEndTime()))
                return new BaseResDto(Status.AMUNDONE);
            appointment.setPreReviewTime(LocalDateTime.now());
        }
        if (status == MyEnum.AM_REVIEWING.getValue()) {
            BygAdmin currentAdmin = AdminUtil.getCurrentAdmin();
            appointment.setReviewerId(currentAdmin.getId()).setReviewingTime(LocalDateTime.now());
        }
        if (status == MyEnum.AM_POST_REVIEW.getValue()) {
//            LambdaQueryWrapper<BygSignIn> wrapper = Wrappers.lambdaQuery(BygSignIn.class)
//                    .eq(BygSignIn::getAmId, appointment.getId())
//                    .and(w -> w.isNotNull(BygSignIn::getNameHit).or().isNotNull(BygSignIn::getContentHit));
//            if (bygSignInService.count(wrapper) > 0) return new BaseResDto(Status.HASUNREVIEWED);
            appointment.setPostReviewTime(LocalDateTime.now());
        }
        appointment.setStatus(status).setUpdateTime(LocalDateTime.now());
        if (! updateById(appointment)) return new BaseResDto(Status.ERROR);
        redisTemplate.delete("amUuId_dm:" + appointment.getUuId());
        redisTemplate.delete("amUuId_status:" + appointment.getUuId());
        redisTemplate.delete("amUuId_isNL:" + appointment.getUuId());
//        if (status == MyEnum.AM_POST_REVIEW.getValue() && profile.equals("prod")) {
//            BygUndertaker taker = bygUndertakerService.getById(appointment.getUndertakerId());
//            smsUtil.noticeSms(taker.getTel(), SMSUtil.Template.TAKERNOTICE, deadMan.getName(), appointment.getUuId());
//        }
        return new BaseResDto(Status.SUCCESS);
    }

    @Override
    public BaseDataResDto<Boolean> isNeedLogin(String amUuId) {
        BaseDataResDto<Boolean> result = RedisUtil.getObject(redisTemplate, "amUuId_isNL:" + amUuId, BaseDataResDto.class);
        if (result == null) {
            synchronized (this) {
                result = RedisUtil.getObject(redisTemplate, "amUuId_isNL:" + amUuId, BaseDataResDto.class);
                if (result == null) {
                    BygAppointment appointment = findAppointmentByUuId(amUuId);
                    if (appointment == null) result = new BaseDataResDto<>(Status.PARAMETERERROR);
                    else result = new BaseDataResDto<Boolean>(Status.SUCCESS).setData(appointment.getStatus() == 4);
                    RedisUtil.setObjectExpire(redisTemplate, "amUuId_isNL:" + amUuId, result);
                }
            }
        }
        return result;
    }

}
